/*
 * Copyright (c) 2005, 2008, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package com.sun.imageio.plugins.gif;

import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.metadata.IIOInvalidTreeException;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.metadata.IIOMetadataFormat;
import javax.imageio.metadata.IIOMetadataFormatImpl;
import org.w3c.dom.Node;

class GIFWritableImageMetadata extends GIFImageMetadata {

    // package scope
    static final String NATIVE_FORMAT_NAME = "javax_imageio_gif_image_1.0";

    GIFWritableImageMetadata() {
        super(true, NATIVE_FORMAT_NAME, "com.sun.imageio.plugins.gif.GIFImageMetadataFormat", null, null);
    }

    public boolean isReadOnly() {
        return false;
    }

    public void reset() {
        // Fields from Image Descriptor
        imageLeftPosition = 0;
        imageTopPosition = 0;
        imageWidth = 0;
        imageHeight = 0;
        interlaceFlag = false;
        sortFlag = false;
        localColorTable = null;

        // Fields from Graphic Control Extension
        disposalMethod = 0;
        userInputFlag = false;
        transparentColorFlag = false;
        delayTime = 0;
        transparentColorIndex = 0;

        // Fields from Plain Text Extension
        hasPlainTextExtension = false;
        textGridLeft = 0;
        textGridTop = 0;
        textGridWidth = 0;
        textGridHeight = 0;
        characterCellWidth = 0;
        characterCellHeight = 0;
        textForegroundColor = 0;
        textBackgroundColor = 0;
        text = null;

        // Fields from ApplicationExtension
        applicationIDs = null;
        authenticationCodes = null;
        applicationData = null;

        // Fields from CommentExtension
        // List of byte[]
        comments = null;
    }

    private byte[] fromISO8859(String data) {
        try {
            return data.getBytes("ISO-8859-1");
        } catch (UnsupportedEncodingException e) {
            return "".getBytes();
        }
    }

    protected void mergeNativeTree(Node root) throws IIOInvalidTreeException {
        Node node = root;
        if (!node.getNodeName().equals(nativeMetadataFormatName)) {
            fatal(node, "Root must be " + nativeMetadataFormatName);
        }

        node = node.getFirstChild();
        while (node != null) {
            String name = node.getNodeName();

            if (name.equals("ImageDescriptor")) {
                imageLeftPosition = getIntAttribute(node, "imageLeftPosition", -1, true, true, 0, 65535);

                imageTopPosition = getIntAttribute(node, "imageTopPosition", -1, true, true, 0, 65535);

                imageWidth = getIntAttribute(node, "imageWidth", -1, true, true, 1, 65535);

                imageHeight = getIntAttribute(node, "imageHeight", -1, true, true, 1, 65535);

                interlaceFlag = getBooleanAttribute(node, "interlaceFlag", false, true);
            } else if (name.equals("LocalColorTable")) {
                int sizeOfLocalColorTable = getIntAttribute(node, "sizeOfLocalColorTable", true, 2, 256);
                if (sizeOfLocalColorTable != 2 && sizeOfLocalColorTable != 4 && sizeOfLocalColorTable != 8 && sizeOfLocalColorTable != 16 && sizeOfLocalColorTable != 32 && sizeOfLocalColorTable != 64 && sizeOfLocalColorTable != 128 && sizeOfLocalColorTable != 256) {
                    fatal(node, "Bad value for LocalColorTable attribute sizeOfLocalColorTable!");
                }

                sortFlag = getBooleanAttribute(node, "sortFlag", false, true);

                localColorTable = getColorTable(node, "ColorTableEntry", true, sizeOfLocalColorTable);
            } else if (name.equals("GraphicControlExtension")) {
                String disposalMethodName = getStringAttribute(node, "disposalMethod", null, true, disposalMethodNames);
                disposalMethod = 0;
                while (!disposalMethodName.equals(disposalMethodNames[disposalMethod])) {
                    disposalMethod++;
                }

                userInputFlag = getBooleanAttribute(node, "userInputFlag", false, true);

                transparentColorFlag = getBooleanAttribute(node, "transparentColorFlag", false, true);

                delayTime = getIntAttribute(node, "delayTime", -1, true, true, 0, 65535);

                transparentColorIndex = getIntAttribute(node, "transparentColorIndex", -1, true, true, 0, 65535);
            } else if (name.equals("PlainTextExtension")) {
                hasPlainTextExtension = true;

                textGridLeft = getIntAttribute(node, "textGridLeft", -1, true, true, 0, 65535);

                textGridTop = getIntAttribute(node, "textGridTop", -1, true, true, 0, 65535);

                textGridWidth = getIntAttribute(node, "textGridWidth", -1, true, true, 1, 65535);

                textGridHeight = getIntAttribute(node, "textGridHeight", -1, true, true, 1, 65535);

                characterCellWidth = getIntAttribute(node, "characterCellWidth", -1, true, true, 1, 65535);

                characterCellHeight = getIntAttribute(node, "characterCellHeight", -1, true, true, 1, 65535);

                textForegroundColor = getIntAttribute(node, "textForegroundColor", -1, true, true, 0, 255);

                textBackgroundColor = getIntAttribute(node, "textBackgroundColor", -1, true, true, 0, 255);

                // XXX The "text" attribute of the PlainTextExtension element
                // is not defined in the GIF image metadata format but it is
                // present in the GIFImageMetadata class. Consequently it is
                // used here but not required and with a default of "". See
                // bug 5082763.

                String textString = getStringAttribute(node, "text", "", false, null);
                text = fromISO8859(textString);
            } else if (name.equals("ApplicationExtensions")) {
                IIOMetadataNode applicationExtension = (IIOMetadataNode) node.getFirstChild();

                if (!applicationExtension.getNodeName().equals("ApplicationExtension")) {
                    fatal(node, "Only a ApplicationExtension may be a child of a ApplicationExtensions!");
                }

                String applicationIDString = getStringAttribute(applicationExtension, "applicationID", null, true, null);

                String authenticationCodeString = getStringAttribute(applicationExtension, "authenticationCode", null, true, null);

                Object applicationExtensionData = applicationExtension.getUserObject();
                if (applicationExtensionData == null || !(applicationExtensionData instanceof byte[])) {
                    fatal(applicationExtension, "Bad user object in ApplicationExtension!");
                }

                if (applicationIDs == null) {
                    applicationIDs = new ArrayList();
                    authenticationCodes = new ArrayList();
                    applicationData = new ArrayList();
                }

                applicationIDs.add(fromISO8859(applicationIDString));
                authenticationCodes.add(fromISO8859(authenticationCodeString));
                applicationData.add(applicationExtensionData);
            } else if (name.equals("CommentExtensions")) {
                Node commentExtension = node.getFirstChild();
                if (commentExtension != null) {
                    while (commentExtension != null) {
                        if (!commentExtension.getNodeName().equals("CommentExtension")) {
                            fatal(node, "Only a CommentExtension may be a child of a CommentExtensions!");
                        }

                        if (comments == null) {
                            comments = new ArrayList();
                        }

                        String comment = getStringAttribute(commentExtension, "value", null, true, null);

                        comments.add(fromISO8859(comment));

                        commentExtension = commentExtension.getNextSibling();
                    }
                }
            } else {
                fatal(node, "Unknown child of root node!");
            }

            node = node.getNextSibling();
        }
    }

    protected void mergeStandardTree(Node root) throws IIOInvalidTreeException {
        Node node = root;
        if (!node.getNodeName().equals(IIOMetadataFormatImpl.standardMetadataFormatName)) {
            fatal(node, "Root must be " + IIOMetadataFormatImpl.standardMetadataFormatName);
        }

        node = node.getFirstChild();
        while (node != null) {
            String name = node.getNodeName();

            if (name.equals("Chroma")) {
                Node childNode = node.getFirstChild();
                while (childNode != null) {
                    String childName = childNode.getNodeName();
                    if (childName.equals("Palette")) {
                        localColorTable = getColorTable(childNode, "PaletteEntry", false, -1);
                        break;
                    }
                    childNode = childNode.getNextSibling();
                }
            } else if (name.equals("Compression")) {
                Node childNode = node.getFirstChild();
                while (childNode != null) {
                    String childName = childNode.getNodeName();
                    if (childName.equals("NumProgressiveScans")) {
                        int numProgressiveScans = getIntAttribute(childNode, "value", 4, false, true, 1, Integer.MAX_VALUE);
                        if (numProgressiveScans > 1) {
                            interlaceFlag = true;
                        }
                        break;
                    }
                    childNode = childNode.getNextSibling();
                }
            } else if (name.equals("Dimension")) {
                Node childNode = node.getFirstChild();
                while (childNode != null) {
                    String childName = childNode.getNodeName();
                    if (childName.equals("HorizontalPixelOffset")) {
                        imageLeftPosition = getIntAttribute(childNode, "value", -1, true, true, 0, 65535);
                    } else if (childName.equals("VerticalPixelOffset")) {
                        imageTopPosition = getIntAttribute(childNode, "value", -1, true, true, 0, 65535);
                    }
                    childNode = childNode.getNextSibling();
                }
            } else if (name.equals("Text")) {
                Node childNode = node.getFirstChild();
                while (childNode != null) {
                    String childName = childNode.getNodeName();
                    if (childName.equals("TextEntry") && getAttribute(childNode, "compression", "none", false).equals("none") && Charset.isSupported(getAttribute(childNode, "encoding", "ISO-8859-1", false))) {
                        String value = getAttribute(childNode, "value");
                        byte[] comment = fromISO8859(value);
                        if (comments == null) {
                            comments = new ArrayList();
                        }
                        comments.add(comment);
                    }
                    childNode = childNode.getNextSibling();
                }
            } else if (name.equals("Transparency")) {
                Node childNode = node.getFirstChild();
                while (childNode != null) {
                    String childName = childNode.getNodeName();
                    if (childName.equals("TransparentIndex")) {
                        transparentColorIndex = getIntAttribute(childNode, "value", -1, true, true, 0, 255);
                        transparentColorFlag = true;
                        break;
                    }
                    childNode = childNode.getNextSibling();
                }
            }

            node = node.getNextSibling();
        }
    }

    public void setFromTree(String formatName, Node root) throws IIOInvalidTreeException {
        reset();
        mergeTree(formatName, root);
    }
}
